//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.WFServiceLibrary
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Threading;
    using System.Linq;
    using Microsoft.Research.DataLayer;
    using Microsoft.Research.eResearch.WFServiceLibrary.SchedulerLogic;
    using System.ServiceProcess;
    using Microsoft.Research.eResearch.Common.Logging;
    using Microsoft.Research.eResearch.Common.Eventing;
    using System.Globalization;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;

    /// <summary>
    /// This class provides the service to poll the registry at regular
    /// intervals to look for new schedules and create the new windows scheduled tasks.
    /// </summary>
    public class WindowsTaskCreator : ILoggable
    {
        /// <summary>
        /// Scheduler service name.
        /// </summary>
        private const string SERVICENAME = "Schedule";

        /// <summary>
        /// Default poll time for scheduler.
        /// </summary>
        private const int DefaultSchedulePollTime = 600000;

        /// <summary>
        /// Handle to the registry.
        /// </summary>
        private Connection registryHandle = null;

        /// <summary>
        /// Name of the instance where the Scheduler will look for
        /// jobs.
        /// </summary>
        private string hostName;

        /// <summary>
        /// Schedule creater EXE Path.
        /// </summary>
        public string SchedulerEXEPath { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public bool IsInteractiveExecution { get; set; }

        /// <summary>
        /// Gets Handle to talk to the registry.
        /// </summary>
        public Connection RegistryHandle
        {
            get { return this.registryHandle; }
        }

        /// <summary>
        /// Gets or sets WF Service Instance Name.
        /// </summary>
        public string HostName
        {
            get { return this.hostName; }
            set { this.hostName = value; }
        }

        /// <summary>
        /// Gets or sets the scheduler poll time.
        /// </summary>
        /// <value>The scheduler poll time.</value>
        public int SchedulerPollTime { get; set; }

        /// <summary>
        /// Poll the Registry to check for new schedules.
        /// </summary>
        public void RunScheduleCreationThread()
        {
            this.Log("Info", "Entered the Schedule Creation");

            // Read from config else use default value.
            int pollTime =
                (this.SchedulerPollTime > 0) ?
                this.SchedulerPollTime :
                WindowsTaskCreator.DefaultSchedulePollTime;

            try
            {
                while (true)
                {
                    if (null != ThreadBase.StaticRegistryHandle)
                    {
                        this.OpenRegistry();
                        break;
                    }

                    Thread.Sleep(5000);
                }
            }
            catch (Exception exp)
            {
                if (null != this.OnTerminateScheduler)
                {
                    this.OnTerminateScheduler("Could not Open Registry for " + this.HostName + " in WindowsTaskCreator due to :" + exp.Message);
                }
                else
                {
                    this.Log("Error", "Could not Open Registry for {0} in WindowsTaskCreator due to : {1}", this.HostName, exp.Message);
                    return;
                }
            }

            while (true)
            {
                try
                {
                    this.ReOpenRegistryConnection();

                    Machine currentNode = ThreadBase.LoadMachine(this.HostName, this.RegistryHandle, this.IsInteractiveExecution);

                    this.UpdateNodeStatus(currentNode);

                    Collection<JobSchedule> pendingSchedule = ThreadBase.GetPendingSchedules(currentNode, this.RegistryHandle);

                    if (null != pendingSchedule && 0 != pendingSchedule.Count)
                    {
                        this.Log("Info", "Found {0} schedules to run.", pendingSchedule.Count.ToString());
                        this.ApplyTaskSchedulerAction(pendingSchedule);
                    }
                }
                catch (RecordNotFoundException recordNotFound)
                {
                    this.Log(
                        "Error",
                        "Machine Instance  {0} is not yet created in WindowsTaskCreator due to : {1}",
                        this.hostName,
                        recordNotFound.Message);
                }
                catch (TridentCustomException exp)
                {
                    this.Log("Error", exp.Message);
                }
                catch (Exception exp)
                {
                    this.Log("Error", "Could not Re-Open registry for {0} in WindowsTaskCreator Due to : {1}", this.HostName, exp.Message);
                }
                finally
                {
                    System.Threading.Thread.Sleep(pollTime);
                }
            }
        }

        /// <summary>
        /// Updates the node status.
        /// </summary>
        private void UpdateNodeStatus(Machine currentNode)
        {
            SchedulerStatus schedulerStatus =
                 (ServiceControllerStatus.Running !=
                 GetWindowsServiceStatus(SERVICENAME)) ? SchedulerStatus.Faulty : SchedulerStatus.Running;

            ThreadBase.UpdateSchedulerServiceStatus(currentNode, schedulerStatus);
        }

        /// <summary>
        /// Gets the windows service status.
        /// </summary>
        /// <param name="serviceName">Name of the service.</param>
        /// <returns></returns>
        private static ServiceControllerStatus GetWindowsServiceStatus(string serviceName)
        {
            ServiceController sc = new ServiceController(serviceName);
            return sc.Status;
        }

        /// <summary>
        /// Applies the task scheduler action.
        /// </summary>
        /// <param name="jobs">The jobs.</param>
        private void ApplyTaskSchedulerAction(Collection<JobSchedule> jobs)
        {
            foreach (JobSchedule job in jobs)
            {
                switch (job.Status)
                {
                    case ScheduleStatus.Pending:
                        this.CreateWindowsTask(job, true);
                        break;
                    case ScheduleStatus.UpdatePending:
                        this.CreateWindowsTask(job, false);
                        break;
                    case ScheduleStatus.Cancelled:
                        this.DeleteWindowsTask(job);
                        break;
                    default:
                        break;
                }
            }
        }

        /// <summary>
        /// Creates the windows task.
        /// </summary>
        /// <param name="schedule">The schedule.</param>
        /// <param name="isNewTask">if set to <c>true</c> [is new task].</param>
        private void CreateWindowsTask(JobSchedule schedule, bool isNewTask)
        {
            ScheduleStatus prevStatus = schedule.Status;
            try
            {
                schedule.Refresh();
                if (schedule.Status != ScheduleStatus.Scheduling && schedule.Status != ScheduleStatus.Scheduled)
                {
                    schedule.Status = ScheduleStatus.Scheduling;
                    schedule.Save();

                    Trigger trigger = null;
                    switch (schedule.Schedule)
                    {
                        case ScheduleType.OneTime:
                            trigger = new TimeTrigger();
                            break;
                        case ScheduleType.Daily:
                            trigger = new DailyTrigger(Convert.ToInt16(schedule.Recurrence));
                            break;
                        case ScheduleType.Weekly:
                            trigger = new WeeklyTrigger(Convert.ToInt16(schedule.Days), Convert.ToInt16(schedule.Recurrence));
                            break;
                        default:
                            break;
                    }

                    if (null != trigger)
                    {
                        trigger.LogMessageReceived += new EventHandler<LogEventArgs>(OnLogMessageReceived);
                        trigger.StartBoundary = schedule.StartTime;

                        if (schedule.IsExpireEnable)
                        {
                            trigger.EndBoundary = schedule.StopTime;
                        }

                        trigger.TaskName = (schedule as IObject).ID.ToString();
                        trigger.TaskDescription = schedule.Name + " | " +
                            schedule.Schedule + " | " +
                            schedule.Description;

                        trigger.ExecutablePath = "\"" + this.SchedulerEXEPath + "\"";
                        trigger.Arguments = "\"" + (schedule as IObject).ID.ToString() + "\"";

                        trigger.CreateTask(isNewTask);

                        trigger.LogMessageReceived -= OnLogMessageReceived;
                        trigger.Dispose();

                        schedule.Status = ScheduleStatus.Scheduled;
                        schedule.Save();
                    }
                }
            }
            catch (Exception exp)
            {
                schedule.Status = prevStatus;
                schedule.Save();

                this.Log(
                    "Error",
                    "Could not create schedule for {0} in WindowsTaskCreator due to : {1}",
                    schedule.Name,
                    exp.Message);
            }
        }

        /// <summary>
        /// Deletes the windows task.
        /// </summary>
        /// <param name="schedule">The schedule.</param>
        private void DeleteWindowsTask(JobSchedule schedule)
        {
            TaskService taskService = new TaskService();

            try
            {
                taskService.RootFolder.DeleteTask((schedule as IObject).ID.ToString());

                ThreadBase.DeleteSelectedSchedule(schedule);
            }
            catch (TridentCustomException exp)
            {
                this.Log("Error", exp.Message);
            }
            catch (Exception)
            {
                this.Log("Error", "Task Schedule is not available to delete.");
            }
        }

        /// <summary>
        /// Open the Registry from config file whihc has Default Connection Set.
        /// </summary>
        private void OpenRegistry()
        {
            if (string.IsNullOrEmpty(this.hostName))
            {
                if (null != this.OnTerminateScheduler)
                {
                    this.OnTerminateScheduler("Agruments not in Correct Format.");
                }
                else
                {
                    this.Log("Error", "Agruments not in Correct Format.");
                    return;
                }
            }

            if (null != ThreadBase.StaticRegistryHandle && null == this.RegistryHandle)
            {
                this.registryHandle = ThreadBase.StaticRegistryHandle.Clone();
                this.registryHandle.Open();
                this.Log("Error", "Opened connection to the registry for WindowsTaskCreator in {0}", this.HostName);
            }
        }

        /// <summary>
        /// This function is used to check and 
        /// reopen the registry connection if it is closed.
        /// </summary>
        private void ReOpenRegistryConnection()
        {
            if (null == this.RegistryHandle || !this.RegistryHandle.Alive)
            {
                ConnectionManager mgr = ConnectionManager.CreateForAgent(
                    ConnectionManager.CancelBehavior.ThrowException,
                    ConnectionManager.Agent.ExecutionService);

                this.registryHandle = mgr.PickConnection(ConnectionManager.ConnectionUI.NeverShowUI);
            }
        }

        /// <summary>
        /// This is the event Handler for the terminate Event.
        /// </summary>
        /// <param name="message">Reason for Termination.</param>
        public delegate void TerminateSchedulerEvent(string message);

        /// <summary>
        /// Event for Terminate.
        /// </summary>
        public event TerminateSchedulerEvent OnTerminateScheduler;

        void OnLogMessageReceived(object sender, LogEventArgs e)
        {
            this.Log(e.Level, e.Message);
        }

        #region ILoggable Members

        /// <summary>
        /// Fired when a log message is received.
        /// </summary>
        public event EventHandler<LogEventArgs> LogMessageReceived;

        /// <summary>
        /// Raises the LogMessageReceived event.
        /// </summary>
        /// <param name="level">The log level.</param>
        /// <param name="message">The log message.</param>
        protected virtual void OnLogMessageReceived(string level, string message)
        {
            this.LogMessageReceived.Fire(this, new LogEventArgs(level, message));
        }

        /// <summary>
        /// Writes the log message.
        /// </summary>
        /// <param name="level">The log level.</param>
        /// <param name="message">Message string to be logged.</param>
        private void Log(string level, string message)
        {
            this.OnLogMessageReceived(level, String.Format(CultureInfo.CurrentUICulture, "WindowsTaskCreator: {0}", message));
        }

        /// <summary>
        /// Writes the log message.
        /// </summary>
        /// <param name="level">The log level.</param>
        /// <param name="format">Format of the message.</param>
        /// <param name="args">Arguments to be formatted.</param>
        private void Log(string level, string format, params object[] args)
        {
            this.Log(level, String.Format(CultureInfo.CurrentUICulture, format, args));
        }

        #endregion
    }
}
